import collectHttpMetrics from '@platformatic/http-metrics'
import os from 'node:os'
import { performance } from 'node:perf_hooks'
import client from '@platformatic/prom-client'

export * as client from '@platformatic/prom-client'

const { eventLoopUtilization } = performance
const { Registry, Gauge, Counter, collectDefaultMetrics } = client

export const kMetricsGroups = Symbol('plt.metrics.MetricsGroups')

export function registerMetricsGroup (registry, group) {
  registry[kMetricsGroups] ??= new Set()
  registry[kMetricsGroups].add(group)
}

export function hasMetricsGroup (registry, group) {
  return registry[kMetricsGroups]?.has(group)
}

// Use this method when dealing with metrics registration in async functions.
// This will ensure that the group is registered only once.
export function ensureMetricsGroup (registry, group) {
  registry[kMetricsGroups] ??= new Set()

  if (registry[kMetricsGroups]?.has(group)) {
    return true
  }

  registry[kMetricsGroups].add(group)
  return false
}

export async function collectThreadCpuMetrics (registry) {
  if (ensureMetricsGroup(registry, 'threadCpuUsage')) {
    return
  }

  let lastSample = process.hrtime.bigint()
  let lastUsage = process.threadCpuUsage()

  const threadCpuUserUsageCounterMetric = new Counter({
    name: 'thread_cpu_user_system_seconds_total',
    help: 'Total user CPU time spent in seconds for the current thread.',
    registers: [registry]
  })

  const threadCpuSystemUsageCounterMetric = new Counter({
    name: 'thread_cpu_system_seconds_total',
    help: 'Total system CPU time spent in seconds for the current thread.',
    registers: [registry]
  })

  const threadCpuPercentUsageGaugeMetric = new Gauge({
    name: 'thread_cpu_percent_usage',
    help: 'The thread CPU percent usage.',
    registers: [registry]
  })

  const threadCpuUsageCounterMetric = new Counter({
    name: 'thread_cpu_seconds_total',
    help: 'Total user and system CPU time spent in seconds for the current threads.',
    // Use this one metric's `collect` to set all metrics' values.
    collect () {
      const newSample = process.hrtime.bigint()
      const newUsage = process.threadCpuUsage()
      const user = newUsage.user - lastUsage.user
      const system = newUsage.system - lastUsage.system
      const elapsed = Number(newSample - lastSample)

      lastUsage = newUsage
      lastSample = newSample

      threadCpuSystemUsageCounterMetric.inc(system / 1e6)
      threadCpuUserUsageCounterMetric.inc(user / 1e6)
      threadCpuUsageCounterMetric.inc((user + system) / 1e6)
      threadCpuPercentUsageGaugeMetric.set((100 * ((user + system) * 1000)) / elapsed)
    },
    registers: [registry]
  })

  registry.registerMetric(threadCpuUserUsageCounterMetric)
  registry.registerMetric(threadCpuSystemUsageCounterMetric)
  registry.registerMetric(threadCpuUsageCounterMetric)
  registry.registerMetric(threadCpuPercentUsageGaugeMetric)
}

export function collectEluMetric (registry) {
  if (ensureMetricsGroup(registry, 'elu')) {
    return
  }

  let startELU = eventLoopUtilization()
  const eluMetric = new Gauge({
    name: 'nodejs_eventloop_utilization',
    help: 'The event loop utilization as a fraction of the loop time. 1 is fully utilized, 0 is fully idle.',
    collect: () => {
      const endELU = eventLoopUtilization()
      const result = eventLoopUtilization(endELU, startELU).utilization
      eluMetric.set(result)
      startELU = endELU
    },
    registers: [registry]
  })
  registry.registerMetric(eluMetric)

  let previousIdleTime = 0
  let previousTotalTime = 0
  const cpuMetric = new Gauge({
    name: 'process_cpu_percent_usage',
    help: 'The process CPU percent usage.',
    collect: () => {
      const cpus = os.cpus()
      let idleTime = 0
      let totalTime = 0

      cpus.forEach(cpu => {
        for (const type in cpu.times) {
          totalTime += cpu.times[type]
          if (type === 'idle') {
            idleTime += cpu.times[type]
          }
        }
      })

      const idleDiff = idleTime - previousIdleTime
      const totalDiff = totalTime - previousTotalTime

      const usagePercent = 100 - (100 * idleDiff) / totalDiff
      const roundedUsage = Math.round(usagePercent * 100) / 100
      cpuMetric.set(roundedUsage)

      previousIdleTime = idleTime
      previousTotalTime = totalTime
    },
    registers: [registry]
  })
  registry.registerMetric(cpuMetric)
}

export async function collectMetrics (applicationId, workerId, metricsConfig = {}, registry = undefined) {
  if (!registry) {
    registry = new Registry()
  }

  const labels = { ...metricsConfig.labels }

  // Use the configured label name
  const labelName = metricsConfig.idLabel || 'applicationId'
  labels[labelName] = applicationId

  if (workerId >= 0) {
    labels.workerId = workerId
  }
  registry.setDefaultLabels(labels)

  if (metricsConfig.defaultMetrics) {
    if (!ensureMetricsGroup(registry, 'default')) {
      collectDefaultMetrics({ register: registry })
    }

    collectEluMetric(registry)
    await collectThreadCpuMetrics(registry)
  }

  if (metricsConfig.httpMetrics && !ensureMetricsGroup(registry, 'http')) {
    collectHttpMetrics(registry, {
      customLabels: ['telemetry_id'],
      getCustomLabels: req => {
        const telemetryId = req.headers?.['x-plt-telemetry-id'] ?? 'unknown'
        return { telemetry_id: telemetryId }
      },
      histogram: {
        name: 'http_request_all_duration_seconds',
        help: 'request duration in seconds summary for all requests',
        collect: function () {
          process.nextTick(() => this.reset())
        }
      },
      summary: {
        name: 'http_request_all_summary_seconds',
        help: 'request duration in seconds histogram for all requests',
        collect: function () {
          process.nextTick(() => this.reset())
        }
      }
    })
  }

  return {
    registry,
    otlpBridge: null
  }
}

export async function setupOtlpExporter (registry, otlpExporterConfig, applicationId) {
  if (!otlpExporterConfig || !otlpExporterConfig.endpoint) {
    return null
  }

  // Check if explicitly disabled
  if (otlpExporterConfig.enabled === false || otlpExporterConfig.enabled === 'false') {
    return null
  }

  // Dynamically import PromClientBridge to defer loading until after telemetry is initialized
  const { PromClientBridge } = await import('@platformatic/promotel')

  const {
    endpoint,
    headers,
    interval = 60000,
    serviceName = applicationId,
    serviceVersion
  } = otlpExporterConfig

  const otlpEndpointOptions = {
    url: endpoint
  }

  if (headers) {
    otlpEndpointOptions.headers = headers
  }

  const conversionOptions = {
    serviceName
  }

  if (serviceVersion) {
    conversionOptions.serviceVersion = serviceVersion
  }

  const bridge = new PromClientBridge({
    registry,
    otlpEndpoint: otlpEndpointOptions,
    interval,
    conversionOptions,
    onError: (error) => {
      // Log error but don't crash the application
      console.error('OTLP metrics export error:', error)
    }
  })

  return bridge
}
